﻿using System;
using System.Collections.Generic;
using System.Linq;
using NAudio.Dsp;

namespace Orpheus.Analyzer
{
    public class DominantNoteAnalyser: TrackAnalyserBase
    {

        private float[] prevBuffer;
        private float[] buffer;

        private int _m;

        private int _samplesCount;
        private int _fftLength;

        private TimeSpan _lastNoteDetected;

        private List<double> prevPitches;
        private int hold;

        public DominantNoteAnalyser(TrackInfo trackInfo, GetCurrentTimeDelegate timeCallback, int fftLength = 1024)
            : base(trackInfo, timeCallback)
        {
            _m = (int)Math.Log(fftLength, 2.0);

            _trackInfo.DominantScore = new TimeStepFunction<NoteGroup>();
            _lastNoteDetected = new TimeSpan(0);

            prevBuffer = new float[fftLength];
            buffer = new float[fftLength];
            _fftLength = fftLength;
        }

        public override void ProcessSamples(float left, float right)
        {
            buffer[_samplesCount] = (left + right) / 2;
            _samplesCount++;

            if (_samplesCount >= _fftLength)
            {
                var pitches = DetectPitches();

                var filterNotePitches = FilterNotePitches(pitches);

                if (filterNotePitches.Any())
                {
                    var notes = new NoteGroup();
                    foreach (var notePitch in filterNotePitches)
                    {
                        Note note = NotePitchHelper.GetNoteByFrequency(notePitch);
                        notes.Notes.Add(note);
                    }

                    TimeSpan currentTime = _GetCurrentTimeCallback();
                    TimeInterval interval = new TimeInterval(_lastNoteDetected, currentTime);

                    _trackInfo.DominantScore.SetValueForInterval(notes, interval);

                    _lastNoteDetected = currentTime;
                }

                _samplesCount = 0;
            }



            //_fftBuffer[_fftPos].X = (float)(left * FastFourierTransform.HammingWindow(_fftPos, _fftBuffer.Length));
            //_fftBuffer[_fftPos].Y = 0;
            //_fftPos++;
            //_samplesCount++;

            //if (_fftPos >= _fftBuffer.Length)
            //{
            //    _fftPos = 0;
            //    // 1024 = 2^10
            //    FastFourierTransform.FFT(true, _m, _fftBuffer);

            //    float binSize = 44100 / _fftBuffer.Length;
            //    int minBin = 1;
            //    int maxBin = (int)(4978.03 / binSize);

            //    float[] notes = new float[12];

            //    float maxIntensity = 0;
            //    int maxBinIndex = 0;
            //    for (int bin = minBin; bin <= maxBin; bin++)
            //    {
            //        Note note = NotePitchHelper.GetNoteByFrequency(binSize*bin);

            //        float intensity = _fftBuffer[bin].X*_fftBuffer[bin].X + _fftBuffer[bin].Y*_fftBuffer[bin].Y;
            //        notes[note.IndexInOctave] += intensity;
                    
            //        //if (intensity >= maxIntensity)
            //        //{
            //        //    maxIntensity = intensity;
            //        //    maxBinIndex = bin;
            //        //}
            //    }

            //    NoteGroup group = new NoteGroup();
            //    float intensityAverage = notes.Sum()/(12);
            //    for(int i = 0; i < notes.Length; i++)
            //    {
            //        if (notes[i] > intensityAverage)
            //        {
            //            group.Notes.Add(new Note() { IndexInOctave = i});
            //        }
            //    }

            //    TimeSpan currentTime = _GetCurrentTimeCallback();
            //    TimeInterval interval = new TimeInterval(_lastNoteDetected, currentTime);

            //    _trackInfo.DominantScore.SetValueForInterval(group,
            //                                                 interval);

            //    //_lastNoteSamples = _samplesCount;
            //    _lastNoteDetected = currentTime;
            //}
        }


        private List<double> FilterNotePitches(double[] notePitches)
        {
            var result = new List<double>();

            for (int i = 0; i < notePitches.Length; i++)
            {
                if (prevPitches.Contains(notePitches[i]))
                {
                    result.Add(notePitches[i]);
                }
            }

            prevPitches.Clear();
            prevPitches.AddRange(notePitches);

            return result;
        }

        private double[] DetectPitches()
        {
            Complex[] fftBuffer = new Complex[_fftLength*2];
            for (int i = 0; i < fftBuffer.Length; i++)
            {
                fftBuffer[i].X =
                    (float)
                    ((i < _fftLength ? prevBuffer[i] : buffer[i - _fftLength]) *
                     FastFourierTransform.HammingWindow(i, fftBuffer.Length));

                fftBuffer[i].Y = 0;
            }

            _m = (int)Math.Log(fftBuffer.Length, 2.0);
            FastFourierTransform.FFT(true, _m, fftBuffer);

            double binSize = 44100.0 / fftBuffer.Length;
            int minBin = (int)(NotePitchHelper.MinPitch / binSize);
            int maxBin = (int)(NotePitchHelper.MaxPitch / binSize);

            //float maxIntensity = 0;
            //int maxBinIndex = 0;

            var intensities = fftBuffer.Select(c => c.Abs());
            var peakBeanIndexes = PeakDetector.DetectPeakIndexes(intensities.ToArray(), minBin, maxBin + 1);

            //for (int bin = minBin; bin <= maxBin; bin++)
            //{
            //    if (bin * binSize < 200)
            //    {
            //        continue;
            //    }

            //    float intensity = fftBuffer[bin].X * fftBuffer[bin].X + fftBuffer[bin].Y * fftBuffer[bin].Y;

            //    if (intensity >= maxIntensity)
            //    {
            //        maxIntensity = intensity;
            //        maxBinIndex = bin;
            //    }
            //}

            buffer.CopyTo(prevBuffer, 0);

            return peakBeanIndexes.Select(bin => bin*binSize).ToArray();
        }

        public override void OnTrackStopped()
        {
            int i = 1;

            while (i < _trackInfo.DominantScore.Intervals.Count)
            {
                TimeInterval prevInterval = _trackInfo.DominantScore.Intervals[i - 1];
                TimeInterval currentInterval = _trackInfo.DominantScore.Intervals[i];

                if (Equals(_trackInfo.DominantScore.GetValue((prevInterval.Start)),
                           _trackInfo.DominantScore.GetValue((currentInterval.Start))))
                {
                    prevInterval.End = currentInterval.End;

                    _trackInfo.DominantScore.ExcludeInterval(currentInterval);
                }
                else
                {
                    i++;
                }
            }
        }

        private TimeSpan ConvertSamplesToTime(int samples)
        {
            double secs = samples/44100.0;
            double msecs = secs%1;

            int fullSeconds = (int)(secs - msecs);

            msecs *= 1000;
            int fullMSeconds = (int) (msecs - msecs%1);

            return new TimeSpan(0, 0, 0, fullSeconds, fullMSeconds);
        }
    }
}
